package edu.pitt.dbmi.odie.gapp.gwt.client.navigator;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.user.client.ui.Widget;
import com.smartgwt.client.core.Rectangle;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.LinkItem;
import com.smartgwt.client.widgets.form.fields.events.ClickEvent;
import com.smartgwt.client.widgets.form.fields.events.ClickHandler;
import com.smartgwt.client.widgets.layout.VLayout;

public class ODIE_DocumentViewer extends VLayout implements ClickHandler {

	private static final int STATE_PLAIN_TEXT = 0;
	private static final int STATE_ANNOTATION_TEXT = 1;

	public static final String CONST_DOCUMENT_TEXT_STYLE = "odie-DocumentText";
	public static final String CONST_ANNOTATION_TEXT_STYLE = "odie-Annotation";
	public static final String CONST_ANNOTATION_SELECTED_STYLE = "odie-Annotation-Selected" ;
	
	private ArrayList<Widget> widgets = new ArrayList<Widget>();

	private int currentLine = -1;
	
	private int totalLines = 0 ;

	private ArrayList<FormItem> annotationLinkItems = new ArrayList<FormItem>();

	private ODIE_NavigatableAnnotation currentAnnotation = null;
	private int currentAnnotationIndex = 0;

	private ODIE_NavigatorPanel navigatorPanel;
	
	private ODIE_NavigatableDocument navigatableDoc = null ;

	private ODIE_MultiLineDocumentLinkManager linkManager = null;

	private ClickHandler doNothingClickHandler = new ClickHandler() {
		public void onClick(ClickEvent event) {
			;
		}
	};

	public ODIE_NavigatorPanel getNavigatorPanel() {
		return navigatorPanel;
	}

	public void setNavigatorPanel(ODIE_NavigatorPanel navigatorPanel) {
		this.navigatorPanel = navigatorPanel;
	}

	private static final Comparator<ODIE_NavigatableAnnotation> myComparator = new Comparator<ODIE_NavigatableAnnotation>() {
		public int compare(ODIE_NavigatableAnnotation o1,
				ODIE_NavigatableAnnotation o2) {
			int ret = 0;
			ret = o1.getStartOffset() - o2.getStartOffset();
			// ret = (ret == 0) ? o1.getEndOffset() - o2.getEndOffset() : ret ;
			return ret;
		}
	};

	private static final Comparator<ODIE_NavigatableAnnotation> overlapComparator = new Comparator<ODIE_NavigatableAnnotation>() {
		public int compare(ODIE_NavigatableAnnotation o1,
				ODIE_NavigatableAnnotation o2) {
			int ret = o1.getStartOffset() - o2.getStartOffset();
			if ((o1.getStartOffset() <= o2.getStartOffset() && o1
					.getEndOffset() >= o2.getStartOffset())
					|| (o1.getEndOffset() >= o2.getStartOffset() && o1
							.getEndOffset() <= o2.getEndOffset())) {
				ret = 0;
			}
			return ret;
		}
	};

	public ODIE_DocumentViewer(ODIE_NavigatorPanel navigatorPanel,
			String documentText,
			ArrayList<ODIE_NavigatableAnnotation> annotations) {
	
		this.navigatorPanel = navigatorPanel;
		TreeSet<ODIE_NavigatableAnnotation> sortedAnnotations = new TreeSet<ODIE_NavigatableAnnotation>(
				overlapComparator);
		sortedAnnotations.addAll(annotations);
		annotations.clear();
		annotations.addAll(sortedAnnotations);
		this.navigatableDoc = new ODIE_NavigatableDocument();
		this.navigatableDoc.setText(documentText);
		this.navigatableDoc.setAnnotations(annotations);
		annotateAndConvertDocument(this.navigatableDoc);
	}

	protected void annotateAndConvertDocument(
			ODIE_NavigatableDocument navigatableDoc) {
		this.currentLine = 0;
		int readingState = STATE_PLAIN_TEXT;
		this.currentAnnotation = null;
		this.linkManager = new ODIE_MultiLineDocumentLinkManager();
		Collection<ODIE_NavigatableAnnotation> annotations = navigatableDoc
				.getAnnotations();
		Iterator<ODIE_NavigatableAnnotation> annotationsIterator = null;
		if (annotations != null && annotations.size() > 0) {
			annotationsIterator = navigatableDoc.getAnnotations().iterator();
			currentAnnotation = (ODIE_NavigatableAnnotation) annotationsIterator
					.next();
		}
		Log.debug(navigatableDoc.getText());
		char[] documentChars = navigatableDoc.getText().toCharArray();
		int currentCharacterIndex = 0;
		ArrayList<Character> htmlBuffer = new ArrayList<Character>();
		while (currentCharacterIndex < documentChars.length) {
			char currentCharacter = documentChars[currentCharacterIndex];
			if (readingState == STATE_PLAIN_TEXT) {
				if (currentAnnotation != null
						&& currentCharacterIndex == currentAnnotation
								.getStartOffset()) {
					readingState = transitionToAnnotationState(htmlBuffer);
				}
			}
			if (readingState == STATE_ANNOTATION_TEXT) {
				if (this.currentAnnotation != null
						&& currentCharacterIndex == this.currentAnnotation
								.getEndOffset() + 1) {
					readingState = transitionToDocumentTextState(
							annotationsIterator, htmlBuffer);
				}
			}
			if (currentCharacter == '\n') {
				String currentStyle = (readingState == STATE_ANNOTATION_TEXT) ? CONST_ANNOTATION_TEXT_STYLE
						: CONST_DOCUMENT_TEXT_STYLE;
				this.linkManager.addClickableFormItem(htmlBuffer, currentStyle,
						doNothingClickHandler);
				htmlBuffer.clear();
				this.linkManager.newRow();
				this.currentLine++;
			} else {
				if (currentCharacter == ' ') {
					appendHtml(htmlBuffer, "&nbsp;");
					// htmlBuffer.add(new Character('_'));
				} else {
					htmlBuffer.add(new Character(currentCharacter));
				}
			}
			currentCharacterIndex++;
		}
		this.linkManager.addFormItem(htmlBuffer, CONST_DOCUMENT_TEXT_STYLE);
		this.linkManager.buildForm(this);
		this.annotationLinkItems = this.linkManager.getFormItemsByStyle(CONST_ANNOTATION_TEXT_STYLE) ;
		this.totalLines = this.currentLine ;
		Log.debug("Created form with " + this.annotationLinkItems.size() + " annotations.") ;	
	}

	protected int transitionToAnnotationState(ArrayList<Character> htmlBuffer) {
		this.linkManager.addFormItem(htmlBuffer, CONST_DOCUMENT_TEXT_STYLE);
		htmlBuffer.clear();
		this.currentAnnotation.setLineOffset(this.currentLine);
		// @todo Need to figure out how color works here.
//		String annotationBackgroundColor = (currentAnnotation.getColor() == null) ? "#CA1F7B"
//				: currentAnnotation.getColor();
		// linkHtmlString += " background-color='" +
		// annotationBackgroundColor + "' " ;
//		linkHtmlString += " background=" + annotationBackgroundColor + " ";
//		linkHtmlString += " >";
		// Log.debug("Annotation link ==> " +
		// linkHtmlString) ;
		// appendHtml(htmlBuffer, linkHtmlString);
		return STATE_ANNOTATION_TEXT;
	}

	protected int transitionToDocumentTextState(
			Iterator<ODIE_NavigatableAnnotation> annotationsIterator,
			ArrayList<Character> htmlBuffer) {
		String idPart = this.currentAnnotation.deriveHtmlIdPart();
		List<FormItem> annotationLinkItems = this.linkManager.addClickableFormItem(
				htmlBuffer, CONST_ANNOTATION_TEXT_STYLE, this);
		int subIdCounter = 0 ;
		for (FormItem formItem : annotationLinkItems) {
			formItem.setTitle(idPart + "-" + subIdCounter) ;
			subIdCounter++ ;
		}
		htmlBuffer.clear();
		if (annotationsIterator.hasNext()) {
			currentAnnotation = (ODIE_NavigatableAnnotation) annotationsIterator
					.next();
		}
		return STATE_PLAIN_TEXT;
	}

	protected ArrayList<LinkItem> getNewLineContainer() {
		ArrayList<LinkItem> result = new ArrayList<LinkItem>();
		return result;
	}

	public boolean remove(Widget child) {
		return widgets.remove(child);
	}

	public Iterator<Widget> iterator() {
		return widgets.iterator();
	}

	protected ArrayList<Character> appendHtml(ArrayList<Character> htmlBuffer,
			String html) {
		char[] htmlArray = html.toCharArray();
		for (int idx = 0; idx < htmlArray.length; idx++) {
			htmlBuffer.add(new Character(htmlArray[idx]));
		}
		return htmlBuffer;
	}

	protected String flushBufferToString(ArrayList<Character> htmlBuffer) {
		String result = "";
		String breakBuffer = "";
		for (Iterator<Character> resultIterator = htmlBuffer.iterator(); resultIterator
				.hasNext();) {
			char c = ((Character) resultIterator.next()).charValue();
			breakBuffer += c;
			result += c;
			if (breakBuffer.equalsIgnoreCase("<br>")) {
				result += "\n";
				breakBuffer = "";
			}
		}
		return result;
	}

	// public void onClick(ClickEvent event) {
	// getAnnotationForWidget((Widget) event.getSource());
	// this.navigatorPanel
	// .positionAnnotation(extractAnnotationIdFromHTMLFragment((Widget) event
	// .getSource()));
	// }

	public void getAnnotationForWidget(FormItem sender) {
		deSelectCurrentAnnotationIndex();
		Iterator<FormItem> iterator = this.annotationLinkItems.iterator();
		int idx = -1;
		while (iterator.hasNext()) {
			idx++;
			if (iterator.next() == sender) {
				this.currentAnnotationIndex = idx;
				this.currentAnnotation = this.navigatableDoc.getAnnotations().get(this.currentAnnotationIndex) ;
				break;
			}
		}
		selectCurrentAnnotationIndex();
	}

	private void selectCurrentAnnotationIndex() {
		styleCurrentAnnotation(CONST_ANNOTATION_SELECTED_STYLE);
		scrollToCurrentAnnotation(this.currentAnnotation);
	}

	private void deSelectCurrentAnnotationIndex() {
		styleCurrentAnnotation("odie-Annotation");
	}

	private void styleCurrentAnnotation(String styleName) {
		if (this.annotationLinkItems != null && annotationLinkItems.size() > 0
				&& this.currentAnnotationIndex < annotationLinkItems.size()) {
			LinkItem htmlAnnotationFragment = (LinkItem) this.annotationLinkItems
					.get(this.currentAnnotationIndex);
			htmlAnnotationFragment.setTextBoxStyle(styleName) ;
		}
	}

	public void scrollToCurrentAnnotation(
			ODIE_NavigatableAnnotation currentAnnotation) {
		if (currentAnnotation != null) {
			//boolean leaveScrollbarGap = this.getLeaveScrollbarGap() ;
			int height = this.getHeight() ;
			int innerHeight = this.getInnerHeight();
			int innerContentHeight = this.getInnerContentHeight() ;
			int minHeight = this.getMinHeight() ;
			int maxHeight = this.getMaxHeight() ;
			int offsetHeight = this.getOffsetHeight() ;
			int offsetY = this.getOffsetY();
			int currentLineFromAnnotation = currentAnnotation.getLineOffset() ;
			int scrollBarSize = this.getScrollbarSize() ; 
			int scrollBottom = this.getScrollBottom() ;
			int scrollHeight = this.getScrollHeight();
			int scrollTop = this.getScrollTop();
			Rectangle rect = this.getRect() ;
			StringBuffer sb = new StringBuffer() ;
			sb.append("rect = " + rect.getTop() + ", " + rect.getHeight()) ;
			sb.append("\n") ;
			sb.append("height = " + height) ;
			sb.append("\n") ;
			sb.append("innerHeight = " + innerHeight) ;
			sb.append("\n") ;
			sb.append("innerContentHeight = " + innerContentHeight) ;
			sb.append("\n") ;
			sb.append("minHeight = " + minHeight) ;
			sb.append("\n") ;
			sb.append("maxHeight = " + maxHeight) ;
			sb.append("\n") ;
			sb.append("offsetHeight = " + offsetHeight) ;
			sb.append("\n") ;
			sb.append("offsetY = " + offsetY) ;
			sb.append("\n") ;
			sb.append("currentLine = " + currentLineFromAnnotation) ;
			sb.append("\n") ;
			sb.append("scrollBarSize = " + scrollBarSize) ;
			sb.append("\n") ;
			sb.append("scrollTop = " + scrollTop) ;
			sb.append("\n") ;
			sb.append("scrollBottom = " + scrollBottom) ;
			sb.append("\n") ;
			sb.append("scrollHeight = " + scrollHeight) ;
			sb.append("\n") ;
			
			
			// scrollToBottom
			// scrollToTop
			// scrollTo
			// scrollBy
			// scrollByPercent
			double currentLine = currentLineFromAnnotation ;
			double position2lineRatio = 1.0d;
			double currentOffsetY = offsetY ;
			if (currentOffsetY > 0.0d) {
				position2lineRatio = currentLine / currentOffsetY ;
			}
			position2lineRatio = 0.048d ;
			
			sb.append("position2lineRatio = " + position2lineRatio) ;
			sb.append("\n") ;
   		    // Log.debug(sb.toString()) ;
			// Log.debug("ss, se, ds, de, al, tl, ratio = "
			// + currentScrollStartPosition + ", "
			// + currentScrollEndPosition + ", "
			// + 0.0 + ", "
			// + currentDisplayEndPosition + ", "
			// + currentLine + ", "
			// + totalLines + ", "
			// + position2lineRatio
			// ) ;
			double currentScrollStartPosition = this.getScrollTop() ;
			double currentScrollEndPosition = this.getInnerHeight() + currentScrollStartPosition ;
			double lineStartOffset = currentScrollStartPosition
					* position2lineRatio;
			double lineEndOffset = currentScrollEndPosition
					* position2lineRatio;
			 Log.debug("Currently displaying lines = "
			 + lineStartOffset + ", "
			 + lineEndOffset
			 ) ;
			double currentPosition = 0.0 ;
			if (currentLine < lineStartOffset) {
				double resetScrollStartPosition = Math.max(currentLine - 5.0d, 0.0d) / position2lineRatio;
				if (resetScrollStartPosition < 0.0) {
					resetScrollStartPosition = 0.0;
				}
				Log.debug("Must scroll to position " + resetScrollStartPosition) ;
				scrollTo(0, (int) resetScrollStartPosition);
			} else if (currentLine > lineEndOffset) {
				double resetScrollStartPosition = (currentLine + 5.0d) / position2lineRatio;
				// Assume overflow is handled by the scroll panel
				// Log.debug("Scrolling forward to to position " +
				// resetScrollStartPosition) ;
				Log.debug("Must scroll to position " + resetScrollStartPosition) ;
				scrollTo(0, (int) resetScrollStartPosition);
			}
		}
	}

	private String extractAnnotationIdFromLinkItem(FormItem widget) {
		// Annotation ids are stored as invisible titles on the links
		//
		String panelAnnotationId = widget.getTitle(); 
		// Chop off annotation information (e.g.,
		// Annotation-0875-0876-937199e7-8230-11de-887a-a929c84b6b87
		//
		String annotationId = panelAnnotationId.substring("Annotation-0875-0876-".length(), panelAnnotationId.length()) ;
		return annotationId ;
	}

	public int getCurrentLine() {
		return currentLine;
	}

	/**
	 * navigateToAnnotation
	 * 
	 * This method helps preserve synchronized state when the Navigator
	 * selects a new annotation in the grid we scroll to the same annotation
	 * in the document viewer.
	 */
	public void navigateToAnnotation(String id) {
		if (this.annotationLinkItems != null
				&& !this.annotationLinkItems.isEmpty()) {
			for (FormItem htmlFragment : this.annotationLinkItems) {
				String fragmentAnnotationId = extractAnnotationIdFromLinkItem(htmlFragment);
				if (fragmentAnnotationId.equals(id)) {
					getAnnotationForWidget(htmlFragment);
					break;
				}
			}
		}
	}
	
	/**
	 * onClick
	 * 
	 * This method responds to annotation clicks in the document viewer and
	 * propagates the information to the Navigator's annotation grid to
	 * keep the two in sync.
	 */
	public void onClick(ClickEvent event) {
		getAnnotationForWidget((LinkItem) event.getSource());
		String annotationId = extractAnnotationIdFromLinkItem((LinkItem) event
				.getSource());
		this.navigatorPanel.positionAnnotation(annotationId);
	}

}
